iT邦幫忙

2024 iThome 鐵人賽

DAY 12
0

哈囉,大家好!經過前面的努力,我們已經設計了資料庫結構,建立了模型,並設定了路由。現在,是時候讓我們深入 Laravel 的 控制器(Controller),將一切串聯起來,為我們的個人財務管理系統打造強大的後端 API。

一、建立控制器

首先,我們需要為每個資源建立對應的控制器,包括 UserController、BankAccountController、CategoryController 和 TransactionController。

1. 使用 Artisan 指令建立控制器

打開你的終端機,進入專案目錄,執行以下指令:

php artisan make:controller UserController
php artisan make:controller BankAccountController
php artisan make:controller CategoryController
php artisan make:controller TransactionController

這會在 app/Http/Controllers 目錄下生成四個控制器檔案。

小提醒:善用 Artisan 指令可以大大提高開發效率,讓我們專注於實際的業務邏輯。

二、實作 UserController

讓我們先從 UserController 開始,實作使用者資源的 CRUD 操作。

1. 列出所有使用者(index 方法)

use App\Models\User;

public function index()
{
    $users = User::all();
    return response()->json($users);
}

這段程式碼相當直觀,我們透過模型取得所有使用者,然後以 JSON 格式回傳。還記得有一次,我需要快速取得所有使用者的列表,這個方法就派上了用場。

2. 建立新使用者(store 方法)

public function store(Request $request)
{
    $validatedData = $request->validate([
        'username' => 'required|unique:users',
        'email' => 'required|email|unique:users',
        'password' => 'required|min:6',
    ]);

    $user = User::create([
        'username' => $validatedData['username'],
        'email' => $validatedData['email'],
        'password' => bcrypt($validatedData['password']),
    ]);

    return response()->json($user, 201);
}

在這裡,我們使用 $request->validate() 進行資料驗證,確保使用者輸入的資料符合我們的規則。還記得當初忘記加上驗證,結果收到一堆奇怪的資料,真是讓人頭疼!

3. 顯示特定使用者(show 方法)

public function show($id)
{
    $user = User::findOrFail($id);
    return response()->json($user);
}

透過 findOrFail,我們可以輕鬆處理找不到資料的情況,避免程式崩潰。這真是 Laravel 體貼的設計啊!

4. 更新使用者資料(update 方法)

public function update(Request $request, $id)
{
    $user = User::findOrFail($id);

    $validatedData = $request->validate([
        'username' => 'sometimes|required|unique:users,username,' . $user->id,
        'email' => 'sometimes|required|email|unique:users,email,' . $user->id,
        'password' => 'sometimes|required|min:6',
    ]);

    if (isset($validatedData['password'])) {
        $validatedData['password'] = bcrypt($validatedData['password']);
    }

    $user->update($validatedData);

    return response()->json($user);
}

這裡特別要注意的是,我們使用了 sometimes 規則,允許部分更新。當初在實作部分更新時,這個規則真是救了我一命!

5. 刪除使用者(destroy 方法)

public function destroy($id)
{
    $user = User::findOrFail($id);
    $user->delete();
    return response()->json(null, 204);
}

刪除資料後,我們回傳 204 No Content,表示請求已成功處理,但沒有內容回傳。

三、實作 BankAccountController

接下來,我們來實作銀行帳戶的控制器方法。

1. 列出所有銀行帳戶(index 方法)

use App\Models\BankAccount;

public function index()
{
    $bankAccounts = BankAccount::all();
    return response()->json($bankAccounts);
}

2. 建立新銀行帳戶(store 方法)

public function store(Request $request)
{
    $validatedData = $request->validate([
        'user_id' => 'required|exists:users,id',
        'account_name' => 'required|string|max:100',
        'account_number' => 'nullable|string|max:50',
        'bank_name' => 'nullable|string|max:100',
        'balance' => 'nullable|numeric',
    ]);

    $bankAccount = BankAccount::create($validatedData);

    return response()->json($bankAccount, 201);
}

在這裡,我們確保了 user_id 必須存在於 users 表中,避免了孤兒資料的產生。

3. 顯示特定銀行帳戶(show 方法)

public function show($id)
{
    $bankAccount = BankAccount::findOrFail($id);
    return response()->json($bankAccount);
}

4. 更新銀行帳戶(update 方法)

public function update(Request $request, $id)
{
    $bankAccount = BankAccount::findOrFail($id);

    $validatedData = $request->validate([
        'account_name' => 'sometimes|required|string|max:100',
        'account_number' => 'nullable|string|max:50',
        'bank_name' => 'nullable|string|max:100',
        'balance' => 'nullable|numeric',
    ]);

    $bankAccount->update($validatedData);

    return response()->json($bankAccount);
}

5. 刪除銀行帳戶(destroy 方法)

public function destroy($id)
{
    $bankAccount = BankAccount::findOrFail($id);
    $bankAccount->delete();
    return response()->json(null, 204);
}

四、實作 CategoryController

現在,我們來為分類資源建立控制器方法。

1. 列出所有分類(index 方法)

use App\Models\Category;

public function index()
{
    $categories = Category::all();
    return response()->json($categories);
}

2. 建立新分類(store 方法)

public function store(Request $request)
{
    $validatedData = $request->validate([
        'user_id' => 'required|exists:users,id',
        'category_name' => 'required|string|max:100',
        'type' => 'required|in:income,expense',
    ]);

    $category = Category::create($validatedData);

    return response()->json($category, 201);
}

3. 顯示特定分類(show 方法)

public function show($id)
{
    $category = Category::findOrFail($id);
    return response()->json($category);
}

4. 更新分類(update 方法)

public function update(Request $request, $id)
{
    $category = Category::findOrFail($id);

    $validatedData = $request->validate([
        'category_name' => 'sometimes|required|string|max:100',
        'type' => 'sometimes|required|in:income,expense',
    ]);

    $category->update($validatedData);

    return response()->json($category);
}

5. 刪除分類(destroy 方法)

public function destroy($id)
{
    $category = Category::findOrFail($id);
    $category->delete();
    return response()->json(null, 204);
}

五、實作 TransactionController

最後,我們來實作交易資源的控制器。

1. 列出所有交易(index 方法)

use App\Models\Transaction;

public function index()
{
    $transactions = Transaction::all();
    return response()->json($transactions);
}

2. 建立新交易(store 方法)

public function store(Request $request)
{
    $validatedData = $request->validate([
        'user_id' => 'required|exists:users,id',
        'bank_account_id' => 'required|exists:bank_accounts,id',
        'category_id' => 'required|exists:categories,id',
        'type' => 'required|in:income,expense',
        'amount' => 'required|numeric',
        'transaction_date' => 'required|date',
        'description' => 'nullable|string|max:255',
    ]);

    $transaction = Transaction::create($validatedData);

    return response()->json($transaction, 201);
}

3. 顯示特定交易(show 方法)

public function show($id)
{
    $transaction = Transaction::findOrFail($id);
    return response()->json($transaction);
}

4. 更新交易(update 方法)

public function update(Request $request, $id)
{
    $transaction = Transaction::findOrFail($id);

    $validatedData = $request->validate([
        'bank_account_id' => 'sometimes|required|exists:bank_accounts,id',
        'category_id' => 'sometimes|required|exists:categories,id',
        'type' => 'sometimes|required|in:income,expense',
        'amount' => 'sometimes|required|numeric',
        'transaction_date' => 'sometimes|required|date',
        'description' => 'nullable|string|max:255',
    ]);

    $transaction->update($validatedData);

    return response()->json($transaction);
}

5. 刪除交易(destroy 方法)

public function destroy($id)
{
    $transaction = Transaction::findOrFail($id);
    $transaction->delete();
    return response()->json(null, 204);
}

六、資料驗證與錯誤處理

在每個控制器中,我們都使用了 $request->validate() 來確保接收到的資料符合我們的預期。這不僅提升了應用程式的健全性,也讓我們更放心地處理資料。

自訂錯誤訊息

如果你想要讓錯誤訊息更貼近使用者,可以這樣做:

$validatedData = $request->validate([
    'email' => 'required|email',
], [
    'email.required' => '電子郵件是必填的喔!',
    'email.email' => '請輸入有效的電子郵件地址。',
]);

我記得有一次,我的客戶反應說錯誤訊息太生硬了,透過自訂錯誤訊息,使用者體驗得到了明顯的改善。

七、與模型的互動

透過控制器,我們可以輕鬆地與模型進行互動,實現各種資料操作。

取得關聯資料

例如,要取得使用者的所有交易紀錄:

public function getUserTransactions($userId)
{
    $user = User::findOrFail($userId);
    $transactions = $user->transactions;
    return response()->json($transactions);
}

這樣的寫法是不是很直觀?Laravel 的 Eloquent ORM 真是讓人愛不釋手!

八、未來的重構與優化

到目前為止,我們的控制器已經能夠正常運作,實現基本的 CRUD 功能。然而,你可能會發現,程式碼中有一些重複的部分,例如資料驗證和錯誤處理。而且,業務邏輯直接寫在控制器中,可能會讓控制器變得臃腫。
這其實很正常,因為我們現在的目標是先讓整個應用程式跑起來,建立一個基本的功能底子。之後,我們會進行 重構,將重複的程式碼抽取出來,優化我們的架構。

為什麼要先寫初版程式碼?

  • 快速迭代:先有一個能跑的版本,可以讓我們更快地看到成果,鼓勵自己繼續前進。
  • 了解需求:透過初版,我們可以更清楚地了解哪些部分需要優化,哪些功能可能需要調整。
  • 避免過度設計:過早優化可能會導致不必要的複雜度,影響開發進度。

未來的重構方向

  • 使用 Form Request:將資料驗證移到專門的類別中,讓控制器更加乾淨。
  • 建立服務層(Service Layer):將業務邏輯從控制器中抽離,提升程式碼的可維護性和可測試性。
  • 實作資源(Resource)和資源集合(Resource Collection):統一 API 回應格式,提升前後端協作效率。

還記得在很久以前剛學Laravel時,一開始也是直接在控制器中寫業務邏輯。後來隨著功能越來越多,程式碼變得難以維護。透過重構,我們將邏輯拆分到不同的類別中,整個專案的結構變得清晰許多,開發效率也大大提升。

九、個人經驗分享

回顧整個開發過程,我深刻體會到控制器在 Laravel 開發中的重要性。它就像是應用程式的指揮官,負責調度各種資源,處理業務邏輯。
然而,直接在控制器中編寫所有邏輯,可能會導致程式碼難以維護。這就是為什麼我們需要考慮未來的重構。透過將重複的部分抽取出來,並將業務邏輯移到適當的位置,我們可以讓程式碼更加乾淨、可讀。
我強烈建議大家在開發初期,專注於讓功能先跑起來,之後再進行重構和優化。這樣可以避免過度設計,同時也能在實踐中發現更好的架構方式。

  • 保持控制器精簡:未來可以將複雜的業務邏輯抽取到服務層,讓控制器專注於處理請求和回應。
  • 使用 Form Request:將驗證規則移到專門的類別中,減少控制器中的重複程式碼。
  • 統一錯誤處理:利用全域的例外處理器,統一處理錯誤訊息,提高應用程式的穩定性。
  • 持續重構:定期檢視和優化程式碼,保持良好的程式品質。

小結

今天,我們深入了解了 Laravel 控制器的實作方式,並為我們的 API 建立了完整的控制器邏輯。透過這些內容,我們可以:

  • 有效地組織業務邏輯,提高程式碼的可讀性和維護性。
  • 確保資料的完整性和安全性,提升使用者體驗。
  • 快速地與模型進行互動,實現各種資料操作。
    同時,我們也為未來的重構和優化奠定了基礎。透過先建立一個可運行的版本,我們可以更清楚地了解需要改進的地方,進而優化我們的應用程式。

在獨自開發的過程中就像是一場有趣的旅程,不僅可以提升了技術能力,也可以對框架有了更深的理解。

Next

接下來,我們將進入 API 測試的階段。畢竟,再好的程式碼也需要經過嚴謹的測試才能確保品質。我們會學習如何使用工具來測試 API,並探討一些進階技巧,讓我們的應用程式更加完善。
同時,在未來的文章中,我們會開始進行重構,將目前的程式碼優化,提升整體的架構和效能。讓我們繼續這段充滿挑戰與樂趣的開發之旅吧!相信透過不斷的學習和實踐,我們都能成為更好的開發者。


上一篇
D11 - 開啟 Laravel 路由設定,打造靈活的 API 入口
下一篇
D13 - 測試不只是找到錯誤:探索單元測試的價值與具體做法
系列文
我獨自開發 - 30天進化之路,掌握 Laravel + Nuxt30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言